
Pass可以在LLVM IR上执行任意的转换。为了说明添加新Pass的机制，我们的新Pass只计算IR指令和基本块的数量，我们将这个Pass命名为countir。将Pass添加到LLVM源树或作为一个独立的Pass略有不同，因此我们将在以下部分中进行这两种操作。让我们从向LLVM源树添加一个新的Pass开始。\par

\hspace*{\fill} \par %插入空行
\textbf{向LLVM源树添加一个Pass}

我们从将新的Pass添加到LLVM源开始。如果稍后想要在LLVM树中发布新的Pass，这是一种正确的方法。\par

在LLVM IR上执行转换的Pass的源代码位于llvm-project/llvm/lib/Transforms文件夹，而头文件位于llvm-project/llvm/include/llvm/Transforms文件夹。因为有这么多的Pass，他们已分类到对应类别的子文件夹。\par

对于我们的新Pass，需要在两个位置都创建了一个名为CountIR的新文件夹。首先，实现CountIR.h头文件:\par

\begin{enumerate}
\item 像往常一样，需要确保文件可以多次包含。另外，我们需要包含Pass管理器定义:
\begin{lstlisting}[caption={}]
#ifndef LLVM_TRANSFORMS_COUNTIR_COUNTIR_H
#define LLVM_TRANSFORMS_COUNTIR_COUNTIR_H

#include "llvm/IR/PassManager.h"
\end{lstlisting}
	
\item 因为在LLVM源代码中，所以可以将新的CountIR类放入LLVM命名空间中。该类继承自PassInfoMixin模板。这个模板只添加了一些样板代码，比如name()方法。不过，它不用于确定Pass的类型:
\begin{lstlisting}[caption={}]
namespace llvm {
class CountIRPass : public PassInfoMixin<CountIRPass> {
\end{lstlisting}
			
\item 在运行时，任务将调用的run()方法。run()方法的签名决定了Pass的类型。这里，第一个参数是函数类型的引用，所以这是一个Pass函数:
\begin{lstlisting}[caption={}]
public:
	PreservedAnalyses run(Function &F,
	FunctionAnalysisManager &AM);
\end{lstlisting}
			
\item 最后，我们需要闭合类、命名空间和头文件的宏:
\begin{lstlisting}[caption={}]
};
} // namespace llvm
#endif
\end{lstlisting}
当然，新Pass的定义非常简单，只执行了一项简单的任务。\par

继续在CountIIR.cpp文件中实现Pass。如果在调试模式下编译，LLVM会收集关于Pass的统计信息。对于我们的Pass，会使用这个基础组件。\par
	
\item 通过包含我们自己的头文件和所需的LLVM头文件来开始编写源代码:
\begin{lstlisting}[caption={}]
#include "llvm/Transforms/CountIR/CountIR.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"
\end{lstlisting}
	
\item 为了缩短源代码，我们告诉编译器我们使用的是llvm名称空间:
\begin{lstlisting}[caption={}]
using namespace llvm;
\end{lstlisting}
	
\item LLVM的内置调试基础设施要求我们定义一个调试类型，它是一个字符串。这个字符串稍后会显示在打印的统计信息中:
\begin{lstlisting}[caption={}]
#define DEBUG_TYPE "countir"
\end{lstlisting}
	
\item 我们用STATISTIC宏定义两个计数器变量。第一个参数是计数器变量的名称，第二个参数是将在统计中打印的文本:
\begin{lstlisting}[caption={}]
STATISTIC(NumOfInst, "Number of instructions.");
STATISTIC(NumOfBB, "Number of basic blocks.");
\end{lstlisting}
	
\item 在run()方法中，我们循环遍历函数的所有基本块，并递增相应的计数器，并对基本块的所有指令都做同样的操作。为了防止编译器对未使用的变量发出警告，我们插入了I变量做空操作。因为我们只计算IR而不更改IR，所以我们告诉调用者使用了Pass，并且保留了所有的分析:
\begin{lstlisting}[caption={}]
PreservedAnalyses
CountIRPass::run(Function &F,
FunctionAnalysisManager &AM) {
	for (BasicBlock &BB : F) {
		++NumOfBB;
		for (Instruction &I : BB) {
			(void)I;
			++NumOfInst;
		}
	}
	return PreservedAnalyses::all();
}
\end{lstlisting}
	
\end{enumerate}

目前为止，已经实现了新Pass的功能。稍后将对out-of-tree Pass重用此实现。对于LLVM树中的解决方案，必须更改LLVM中的几个文件来声明新的Pass:\par

\begin{enumerate}
\item 首先，需要将CMakeLists.txt添加到源文件夹。这个文件包含一个新的LLVM库名称LLVM\allowbreak CountIR的构建说明。新库需要链接到LLVM Support组件，因为我们使用了调试和统计基础设施，还需要链接到LLVM Core组件，其中包含LLVM IR的定义:
\begin{tcolorbox}[colback=white,colframe=black]
add\underline{~}llvm\underline{~}component\underline{~}library(LLVMCountIR \\
\hspace*{0.5cm}CountIR.cpp \\
\hspace*{0.5cm}LINK\underline{~}COMPONENTS Core Support )
\end{tcolorbox}
	
\item 为了使这个新的库成为构建的一部分，我们需要将这个文件夹添加到父文件夹的CMakeList.txt中，即llvm-project/llvm/lib/Transforms/CMakeList.txt文件。然后，添加以下行:
\begin{tcolorbox}[colback=white,colframe=black]
add\underline{~}subdirectory(CountIR)
\end{tcolorbox}
	
\item PassBuilder类需要知道我们的新Pass。为此，在llvm-project/llvm/lib/Passes/PassBuilder.cpp文件的include部分添加以下代码:
\begin{lstlisting}[caption={}]
#include "llvm/Transforms/CountIR/CountIR.h
\end{lstlisting}
	
\item 最后一步，需要更新Pass注册表，它位于ellvmproject/llvm/lib/Passes/PassRegistry.def文件中。查找定义Pass函数的部分，例如：通过搜索function \underline{~}PASS宏。本节中，需要添加以下行:
\begin{lstlisting}[caption={}]
FUNCTION_PASS("countir", CountIRPass())
\end{lstlisting}
	
\item 我们现在已经做了所有必要的改变。按照第1章的构建说明，使用CMake重新编译LLVM。为了测试新的Pass，我们在演示中存储以下IR代码。Ll文件在构建文件夹中。代码有两个函数，三个指令和两个基本块:
\begin{tcolorbox}[colback=white,colframe=black]
define internal i32 @func() \{ \\
\hspace*{0.5cm}ret i32 0 \\
\} \\
\\
define dso\underline{~}local i32 @main() \{ \\
\hspace*{0.5cm}	\%1 = call i32 @func() \\
\hspace*{0.5cm}	ret i32 \%1 \\
\}
\end{tcolorbox}
	
\item 我们可以通过opt实用程序使用新Pass。要运行新Pass，我们要使用\verb|--|passes="countir"选项。要获得统计输出，需要添加\verb|--|stats选项。因为我们不需要生成的比特码，所以我们也指定了 \verb|--|disable-output选项:
\begin{tcolorbox}[colback=white,colframe=black]
\$ bin/opt \verb|--|disable-output \verb|--|passes="countir" \verb|--|stats  \\
demo.ll \\
\verb|===------------------------------------------------------| \\
\verb|--===| \\
... Statistics Collected ... \\
\verb|===------------------------------------------------------| \\
\verb|--===| \\
2 countir - Number of basic blocks. \\
3 countir - Number of instructions.
\end{tcolorbox}
	
\item 运行我们的新Pass，其输出符合期望。我们已经成功扩展了LLVM!
	
\end{enumerate}

运行单个Pass有助于调试。使用\verb|--|passes选项，不仅可以命名单个Pass，还可以描述整个流水。例如，优化级别2的默认管道名为default<O2>。可以使用\verb|--|passes="module(countir),default<O2>"参数在默认管道之前运行countir Pass，这样的流水描述中的Pass名称必须是相同类型的。默认的流水是一个模块Pass，我们的countir Pass是一个Pass函数。要从两者创建模块管道。首先，必须创建一个包含countir Pass的Pass模块。通过模块(countir)可以完成，通过在逗号分隔的列表中指定函数，可以向模块Pass中添加更多的Pass函数。以同样的方式，可以组合Pass模块。想要研究效果的话，可以使用内联和countir Pass，以不同的顺序运行它们，或者作为Pass模块，结果会是不同的统计输出。\par

如果您计划将Pass作为LLVM的一部分发布，那么向LLVM源树中添加一个新的Pass是有意义的。如果不打算这样做，或者想独立于LLVM分发Pass，那么可以创建一个Pass插件。下一节中，我们将查看执行此操作的步骤。\par


\hspace*{\fill} \par %插入空行
\textbf{添加一个新Pass作为插件}

为了提供一个新的Pass作为插件，我们将创建一个使用LLVM的新项目:\par

\begin{enumerate}
\item 我们首先在源文件夹中创建一个名为countirpass的文件夹。该文件夹将具有以下结构和文件:
\begin{tcolorbox}[colback=white,colframe=black]
|\verb|--| CMakeLists.txt \\
|\verb|--| include \\
|\hspace{1cm}|\verb|--| CountIR.h \\
|\verb|--| lib \\
\hspace*{0.8cm}|\verb|--| CMakeLists.txt \\
\hspace*{0.8cm}|\verb|--| CountIR.cpp
\end{tcolorbox}
	
\item 注意，我们重用了前一节中的功能，并进行了一些调整。CountIR.h头文件现在位于不同的位置，因此我们更改了用作保护宏的名称。我们也不使用llvm名称空间，因为现在在llvm源之外。头文件如下所示:
\begin{lstlisting}[caption={}]
#ifndef COUNTIR_H
#define COUNTIR_H

#include "llvm/IR/PassManager.h"

class CountIRPass
: public llvm::PassInfoMixin<CountIRPass> {
	public:
	llvm::PreservedAnalyses
	run(llvm::Function &F,
	llvm::FunctionAnalysisManager &AM);
};

#endif
\end{lstlisting}
	
\item 可以复制上一节中的CountIR.cpp实现文件。这里也需要一些小的变动。因为头文件已经改变了，需要用下面的代码替换include指令:
\begin{lstlisting}[caption={}]
#include "CountIR.h"
\end{lstlisting}
	
\item 我们还需要在Pass构建器中注册新Pass，当加载插件时就会发生这种情况。Pass插件管理器调用特殊函数llvmGetPassPluginInfo()，该函数会执行注册。对于这个实现，需要额外的包含两个文件:
\begin{lstlisting}[caption={}]
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
\end{lstlisting}
用户使用\verb|–-|passes选项指定要在命令行上运行的Pass。PassBuilder类从字符串中提取Pass名称。为了创建一个名为Pass的实例，PassBuilder类维护一个回调列表。其实，调用回调时使用的是Pass名称和Pass管理器。如果回调知道Pass名称，那么将此Pass的一个实例添加到Pass管理器中。对于Pass，需要提供这样一个回调函数:
\begin{lstlisting}[caption={}]
bool PipelineParsingCB(
StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
	if (Name == "countir") {
		FPM.addPass(CountIRPass());
		return true;
	}
	return false;
}
\end{lstlisting}
	
\item 当然，需要将这个函数注册为PassBuilder实例。加载插件后，注册回调函数就是为了这个目的。我们的注册功能如下:
\begin{lstlisting}[caption={}]
void RegisterCB(PassBuilder &PB) {
	PB.registerPipelineParsingCallback(PipelineParsingCB);
}
\end{lstlisting}
	
\item 最后，每个插件都需要提供前面提到的llvmGetPassPluginInfo()函数。这个函数返回一个包含四个元素的结构:我们的插件使用的LLVM插件API版本、一个名称、插件的版本号和注册回调。插件API要求函数使用extern“C”。这是为了避免C++修饰命名的问题。功能非常简单:
\begin{lstlisting}[caption={}]
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_
WEAK
llvmGetPassPluginInfo() {
	return {LLVM_PLUGIN_API_VERSION, "CountIR", "v0.1",
		RegisterCB};
}
\end{lstlisting}
为每个回调函数实现一个单独的函数有助于理解发生了什么。如果插件提供了几个Pass，那么可以扩展RegisterCB回调函数来注册所有Pass。通常，可以找到一种紧凑的方法。下面的llvmGetPassPluginInfo()函数将前面的PipelineParsingCB()、RegisterCB()和llvmGetPass\allowbreak PluginInfo()组合成一个函数，并通过Lambda函数来实现:
\begin{lstlisting}[caption={}]
extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_
WEAK
llvmGetPassPluginInfo() {
	return {LLVM_PLUGIN_API_VERSION, "CountIR", "v0.1",
		[](PassBuilder &PB) {
			PB.registerPipelineParsingCallback(
			[](StringRef Name, FunctionPassManager 
			&FPM,
			ArrayRef<PassBuilder::PipelineElement>) 
			{
				if (Name == "countir") {
					FPM.addPass(CountIRPass());
					return true;
				}
				return false;
			});
	}};
}
\end{lstlisting}
	
\item 现在，只需要添加构建文件。这个lib/CMakeLists.txt文件只包含一个编译源文件的命令。特定于llvm的命令add\underline{~}llvm\underline{~}library()确保使用了与构建llvm时相同的编译器标志:
\begin{tcolorbox}[colback=white,colframe=black]
add\underline{~}llvm\underline{~}library(CountIR MODULE CountIR.cpp)
\end{tcolorbox}
顶层CMakeLists.txt文件更复杂。
	
\item 通常，我们设置了所需的CMake版本和项目名称。另外，将LLVM\underline{~}EXPORTED\underline{~}SYMBOL\underline{~}FILE变量设置为ON。这是插件在Windows上工作的必要条件:
\begin{tcolorbox}[colback=white,colframe=black]
cmake\underline{~}minimum\underline{~}required(VERSION 3.4.3) \\
project(countirpass)\\
\\
set(LLVM\underline{~}EXPORTED\underline{~}SYMBOL\underline{~}FILE ON)
\end{tcolorbox}
	
\item 接下来，安装LLVM。我们还要找到的版本信息，并打印到控制台:
\begin{tcolorbox}[colback=white,colframe=black]
find\underline{~}package(LLVM REQUIRED CONFIG) \\
message(STATUS "Found LLVM \$\{LLVM\underline{~}PACKAGE\underline{~}VERSION\}") \\
message(STATUS "Using LLVMConfig.cmake in: \$\{LLVM\underline{~}DIR\}")
\end{tcolorbox}
	
\item 现在，可以将LLVM中的cmake文件夹添加到搜索路径中。包括了特定于llvm的文件ChooseMSVCCRT和AddLLVM，并提供了额外的命令:
\begin{tcolorbox}[colback=white,colframe=black]
list(APPEND CMAKE\underline{~}MODULE\underline{~}PATH \$\{LLVM\underline{~}DIR\}) \\
include(ChooseMSVCCRT) \\
include(AddLLVM)
\end{tcolorbox}
	
\item 编译器需要知道所需的定义和LLVM的路径:
\begin{tcolorbox}[colback=white,colframe=black]
include\underline{~}directories("\$\{LLVM\underline{~}INCLUDE\underline{~}DIR\}") \\
add\underline{~}definitions("\$\{LLVM\underline{~}DEFINITIONS\}") \\
link\underline{~}directories("\$\{LLVM\underline{~}LIBRARY\underline{~}DIR\}")
\end{tcolorbox}
	
\item 最后，我们添加自己的include和source文件夹:
\begin{tcolorbox}[colback=white,colframe=black]
include\underline{~}directories(BEFORE include) \\
add\underline{~}subdirectory(lib)
\end{tcolorbox}
	
\item 实现了所有必需的文件之后，现在可以在countirpass文件夹旁创建build文件夹了。首先，切换到构建目录并创建构建文件:
\begin{tcolorbox}[colback=white,colframe=black]
\$ cmake –G Ninja ../countirpass
\end{tcolorbox}
	
\item 然后，可以编译插件:
\begin{tcolorbox}[colback=white,colframe=black]
\$ ninja
\end{tcolorbox}
	
\item 使用插件与opt工具，这是模块化的LLVM优化器和分析器。其中，opt生成输入文件的优化版本。使用插件时，需要指定一个参数来加载插件:
\begin{tcolorbox}[colback=white,colframe=black]
\$ opt \verb|--|load-pass-plugin=lib/CountIR.so \\
\verb|--|passes="countir"$\setminus$ \\
\hspace*{0.5cm}\verb|--|disable-output \verb|–-|stats demo.ll
\end{tcolorbox}
	
\end{enumerate}

输出与以前的版本相同。恭喜你，Pass插件工作了!\par

到目前为止，我们只为新Pass管理器创建了一个Pass。在下一节中，我们还将扩展旧Pass管理器中的Pass。\par




















